#include <iostream>

using namespace std;

#include "cppTutorSocket.h"

#include <string.h>
#include <errno.h>
#include <fcntl.h>

#include "cppTutorLog.h"
extern CcppTutorLog Log;

Socket :: Socket() : m_sock ( -1 )
{
    Log << "ENTER : Socket :: Socket()" << endl;
#ifdef WIN32
    //windows 
#else
    memset ( &m_addr, 0, sizeof (m_addr));
#endif
    Log << "EXIT  : Socket :: Socket()" << endl;
}

Socket :: ~Socket()
{
    Log << "ENTER : Socket :: ~Socket()" << endl;
#ifdef WIN32
    //windows 
#else
    if ( is_valid() )
    ::close ( m_sock );
#endif
    Log << "EXIT  : Socket :: ~Socket()" << endl;
}

bool Socket :: socket()
{
    Log << "ENTER : Socket :: socket()" << endl;
#ifdef WIN32
    //windows 
#else
    m_sock = ::socket (AF_INET, SOCK_STREAM, 0);
#endif
    if (m_sock == -1)
    {
        Log << "EXIT  : Socket :: socket() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }

    // TIME_WAIT - argh
    int on = 1;
#ifdef WIN32
    //windows
#else
    if (setsockopt ( m_sock, SOL_SOCKET, SO_REUSEADDR, ( const char* ) &on, sizeof ( on ) ) == -1)
#endif
    {
        Log << "EXIT  : Socket :: socket() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }
    Log << "EXIT  : Socket :: socket() [" << true << " : " << __LINE__ << "]"<< endl;
    return true;
}

bool Socket :: bind ( const int port )
{
#ifdef WIN32
    //windows 
    Log << "EXIT  : Socket :: bind() [" << true << " : " << __LINE__ << "]"<< endl;
    return true;
#else
    if (m_sock == -1)
    {
        Log << "EXIT  : Socket :: bind() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }
    m_addr.sin_family       = AF_INET;
    m_addr.sin_addr.s_addr  = INADDR_ANY;
    m_addr.sin_port         = htons (port);
    int bind_return = 0;
    bind_return = :: bind (m_sock, (struct sockaddr *) &m_addr, sizeof ( m_addr ));
    if ( bind_return == -1 )
    {
        Log << "EXIT  : Socket :: bind() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }
#endif
    Log << "EXIT  : Socket :: bind() [" << true << " : " << __LINE__ << "]"<< endl;
    return true;
}

bool Socket :: listen() const
{
    Log << "ENTER : Socket :: listen()" << endl;
#ifdef WIN32
    //windows 
    Log << "EXIT  : Socket :: listen() [" << true << " : " << __LINE__ << "]"<< endl;
    return true;
#else
    if (m_sock == -1)
    {
        Log << "EXIT  : Socket :: listen() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }
    int listen_return = :: listen (m_sock, MAXCONNECTIONS);
    if ( listen_return == -1 )
    {
        Log << "EXIT  : Socket :: listen() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }
#endif
    Log << "EXIT  : Socket :: listen() [" << true << " : " << __LINE__ << "]"<< endl;
    return true;
}

bool Socket :: accept (Socket& new_socket) const
{
    Log << "ENTER : Socket :: accept()" << endl;
#ifdef WIN32
    //windows 
    Log << "EXIT  : Socket :: accept() [" << true << " : " << __LINE__ << "]"<< endl;
    return true;
#else
    int addr_length = sizeof ( m_addr );
    new_socket.m_sock = :: accept ( m_sock, ( sockaddr * ) &m_addr, ( socklen_t * ) &addr_length );

    if ( new_socket.m_sock <= 0 )
    {
        Log << "EXIT  : Socket :: accept() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }
    else
    {
        Log << "EXIT  : Socket :: accept() [" << true << " : " << __LINE__ << "]"<< endl;
        return true;
    }
    #endif
    
    Log << "EXIT  : Socket :: accept() [" << true << " : " << __LINE__ << "]"<< endl;
    return true;
}

bool Socket::send ( const std::string s ) const
{
    Log << "ENTER : Socket :: send()" << endl;
#ifdef WIN32
    //windows 
    Log << "EXIT  : Socket :: send() [" << true << " : " << __LINE__ << "]"<< endl;
    return true;
#else
    int status = :: send ( m_sock, s.c_str(), s.size(), MSG_NOSIGNAL );
    if ( status == -1 )
    {
        Log << "EXIT  : Socket :: send() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }
    else
    {
        Log << "EXIT  : Socket :: send() [" << true << " : " << __LINE__ << "]"<< endl;
        return true;
    }
#endif
    Log << "EXIT  : Socket :: send() [" << true << " : " << __LINE__ << "]"<< endl;
    return true;
}

int Socket::recv ( std::string& s ) const
{
    Log << "ENTER : Socket :: recv()" << endl;
 #ifdef WIN32
    //windows 
    Log << "EXIT  : Socket :: recv() [" << 1 << " : " << __LINE__ << "]"<< endl;
    return 1;
#else  
    char buf [ MAXRECV + 1 ];

    s = "";
    memset ( buf, 0, MAXRECV + 1 );
    int status = ::recv ( m_sock, buf, MAXRECV, 0 );

    if ( status == -1 )
    {
        Log << "status == -1   errno == " << errno << "  in Socket::recv" << endl;
        Log << "EXIT  : Socket :: recv() [" << 0 << " : " << __LINE__ << "]"<< endl;
        return 0;
    }
    else if ( status == 0 )
    {
        Log << "EXIT  : Socket :: recv() [" << 0 << " : " << __LINE__ << "]"<< endl;
        return 0;
    }
    else
    {
        s = buf;
        Log << "EXIT  : Socket :: recv() [" << status << " : " << __LINE__ << "]"<< endl;
        return status;
    }
#endif
    Log << "EXIT  : Socket :: recv() [" << 0 << " : " << __LINE__ << "]"<< endl;
}

bool Socket :: connect ( const std::string host, const int port )
{
    Log << "ENTER : Socket :: connect()" << endl;
 #ifdef WIN32
    //windows 
    Log << "EXIT  : Socket :: connect() [" << true << " : " << __LINE__ << "]"<< endl;
    return true;
#else  
    if (m_sock == -1)
    {
        Log << "EXIT  : Socket :: connect() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }

    m_addr.sin_family = AF_INET;
    m_addr.sin_port = htons ( port );

    int status = inet_pton ( AF_INET, host.c_str(), &m_addr.sin_addr );

    if ( errno == EAFNOSUPPORT ) 
    {   
        Log << "EXIT  : Socket :: connect() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }

    status = ::connect ( m_sock, ( sockaddr * ) &m_addr, sizeof ( m_addr ) );

    if ( status == 0 )
    {
        Log << "EXIT  : Socket :: connect() [" << true << " : " << __LINE__ << "]"<< endl;
        return true;
    }
    else
    {
        Log << "EXIT  : Socket :: connect() [" << false << " : " << __LINE__ << "]"<< endl;
        return false;
    }
#endif
    Log << "EXIT  : Socket :: connect() [" << true << " : " << __LINE__ << "]"<< endl;
}

void Socket::set_non_blocking ( const bool b )
{
    Log << "ENTER : Socket :: set_non_blocking()" << endl;
#ifdef WIN32
    //windows 
#else
    int opts;

    opts = fcntl ( m_sock, F_GETFL );

    if ( opts < 0 )
    {
        return;
    }

    if ( b )
    {
        opts = ( opts | O_NONBLOCK );
    }
    else
    {
        opts = ( opts & ~O_NONBLOCK );
    }

    fcntl ( m_sock, F_SETFL,opts );
#endif
    Log << "EXIT  : Socket :: set_non_blocking() [" << " : " << __LINE__ << "]"<< endl;
}
